package cn.jhz.learn.community_dynamic.security.filter;

import cn.jhz.learn.community_dynamic.security.helper.LoginHelper;
import cn.jhz.learn.community_dynamic.security.model.AccountType;
import cn.jhz.learn.community_dynamic.security.model.JwtAuthenticationToken;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final RequestMatcher requiresAuthenticationRequestMatcher;
    private final AuthenticationSuccessHandler successHandler;
    private final AuthenticationFailureHandler failureHandler;
    private final List<RequestMatcher> permissiveRequestMatchers;
    private AuthenticationManager authenticationManager;

    @Autowired
    public JwtAuthenticationFilter(@Lazy AuthenticationManager authenticationManager,
	    @Qualifier("jwtRefreshSuccessHandler") AuthenticationSuccessHandler successHandler,
	    AuthenticationFailureHandler failureHandler, 
	    @Qualifier("permissiveRequestMatchers") List<RequestMatcher> permissiveRequestMatchers) {
	this.authenticationManager = authenticationManager;
	this.requiresAuthenticationRequestMatcher = new RequestHeaderRequestMatcher("Authorization");
	// 拦截header中带Authorization的请求
	this.successHandler = successHandler;
	this.failureHandler = failureHandler;
	this.permissiveRequestMatchers = permissiveRequestMatchers;
    }

    @Override
    public void afterPropertiesSet() {
	Assert.notNull(authenticationManager, "authenticationManager must be specified");
	Assert.notNull(successHandler, "AuthenticationSuccessHandler must be specified");
	Assert.notNull(failureHandler, "AuthenticationFailureHandler must be specified");
    }

    protected String getJwtToken(HttpServletRequest request) {
	String authInfo = request.getHeader("Authorization");
	return StringUtils.removeStart(authInfo, "Bearer ");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
	    throws ServletException, IOException {
	
	// header没带token的，直接放过，因为部分url匿名用户也可以访问
	// 如果匿名用户的请求没带token，这里放过也没问题，因为SecurityContext中没有认证信息，后面会被权限控制模块拦截
	if (!requiresAuthentication(request, response)) {
	    filterChain.doFilter(request, response);
	    return;
	}
	LoginHelper.setAccountType(AccountType.JWT);

	Authentication authResult = null;
	AuthenticationException failed = null;
	try {
	    // 从头中获取token并封装后提交给AuthenticationManager
	    String token = getJwtToken(request);
	    if (StringUtils.isNotBlank(token)) {
		JwtAuthenticationToken authToken = new JwtAuthenticationToken(token);
		authResult = this.getAuthenticationManager().authenticate(authToken);
	    } else {
		// 如果token长度为0
		failed = new InsufficientAuthenticationException("JWT is Empty");
	    }
	} catch (InternalAuthenticationServiceException e) {
	    logger.error("An internal error occurred while trying to authenticate the user.", failed);
	    failed = e;
	} catch (AuthenticationException e) {
	    // Authentication failed
	    failed = e;
	}
	if (authResult != null) {
	    // token认证成功
	    successfulAuthentication(request, response, filterChain, authResult);
	}
        else if(permissiveRequest(request)){ //token认证失败，并且这个request在权限列表内，才会返回错误
            unsuccessfulAuthentication(request, response, failed);
            return;
        }

	filterChain.doFilter(request, response);
    }

    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
	    AuthenticationException failed) throws IOException, ServletException {
	SecurityContextHolder.clearContext();
	failureHandler.onAuthenticationFailure(request, response, failed);
    }

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
	    Authentication authResult) throws IOException, ServletException {
	SecurityContextHolder.getContext().setAuthentication(authResult);
	successHandler.onAuthenticationSuccess(request, response, authResult);
    }

    protected AuthenticationManager getAuthenticationManager() {
	return authenticationManager;
    }

    
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
       this.authenticationManager = authenticationManager;

    }

    protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
	return requiresAuthenticationRequestMatcher.matches(request);
    }

    protected boolean permissiveRequest(HttpServletRequest request) {
	if (permissiveRequestMatchers == null)
	    return false;
	for (RequestMatcher permissiveMatcher : permissiveRequestMatchers) {
//	    System.out.println(permissiveMatcher);
//	    System.out.println(request.getRequestURI());
	    if (permissiveMatcher.matches(request))
		return true;
	}
	return false;
    }

    public AuthenticationFailureHandler getFailureHandler() {
        return failureHandler;
    }
    
}
